/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.message;

import java.util.HashMap;
import java.util.Map;

public final class MessageFactory {
	/**
	 * This map contains all known classes that extend the abstract Message
	 * class. It relates the received IRC command to a specific class that can
	 * take over the remaining parsing of the message.
	 */
	private static Map<String, Class<? extends IncomingMessage>> messageTypeMap;

	// Any type not defined here will default to UnknownMessage.
	static {
		messageTypeMap = new HashMap<String, Class<? extends IncomingMessage>>();
		messageTypeMap.put(MessageType.WELCOME_MESSAGE.toString(), WelcomeMessage.class);
		messageTypeMap.put(MessageType.NICKNAME_IN_USE.toString(), NicknameInUseMessage.class);
		messageTypeMap.put(MessageType.PING.toString(), PingMessage.class);
		messageTypeMap.put(MessageType.PRIVATE_MESSAGE.toString(), PrivateMessage.class);
	}

	/**
	 * Static class.
	 */
	private MessageFactory() {
	}

	/**
	 * This factory method will determine the class the given message belongs to
	 * and delegates further processing. When done, it will return the new
	 * Message object. Returns null in case of an error.
	 * 
	 * @param message
	 *            The received message from the IRC server.
	 * @return An instance of Message with the information contained in the
	 *         given message.
	 * @throws Exception
	 *             If the message class could not be instantiated.
	 * @throws Exception
	 *             If the message class threw some sort of Exception during
	 *             parsing.
	 * @throws MessageParseException
	 *             If the message class didn't set the message type.
	 */
	public static IncomingMessage create(String message) throws MessageParseException {
		String command;
		int afterFirstSpace = message.indexOf(' ') + 1;
		if (message.charAt(0) == ':') {
			command = message.substring(afterFirstSpace, message.indexOf(' ', afterFirstSpace));
		} else {
			command = message.substring(0, afterFirstSpace - 1);
		}

		IncomingMessage instance;
		Class<? extends IncomingMessage> messageClass = UnknownMessage.class;
		if (messageTypeMap.containsKey(command)) {
			messageClass = messageTypeMap.get(command);
		}

		try {
			instance = messageClass.newInstance();
		} catch (Exception e) {
			throw new MessageParseException("Could not instantiate " + messageClass + " for " + command + ".", e);
		}

		try {
			instance.parse(message);
		} catch (Exception e) {
			throw new MessageParseException("Error parsing the message \"" + message + "\".", e);
		}

		// Check if the message type has been set. If not, we have a bug.
		if (instance.getMessageType() == null) {
			throw new MessageParseException("Message subclass " + messageClass
					+ " didn't set the message type. Discarding.");
		}

		return instance;
	}

}
